// encoding: UTF-8
/******************************************************************************
*                                                                             *
*                                                                             *
* MiniPath - Notepad3 Explorer Plugin                                         *
*                                                                             *
* Helpers.c                                                                   *
*   General helper functions                                                  *
*   Based on code from metapath, (c) Florian Balmer 1996-2011                 *
*                                                                             *
*                                                  (c) Rizonesoft 2008-2024   *
*                                                    https://rizonesoft.com   *
*                                                                             *
*                                                                             *
*******************************************************************************/
#if !defined(WINVER)
#define WINVER 0x601  /*_WIN32_WINNT_WIN7*/
#endif
#if !defined(_WIN32_WINNT)
#define _WIN32_WINNT 0x601  /*_WIN32_WINNT_WIN7*/
#endif
#if !defined(NTDDI_VERSION)
#define NTDDI_VERSION 0x06010000  /*NTDDI_WIN7*/
#endif
//~#define VC_EXTRALEAN 1
//~#define WIN32_LEAN_AND_MEAN 1
#define NOMINMAX 1
#include <windows.h>
#include <shlobj.h>
#include <shlwapi.h>
#include <commctrl.h>
#include <uxtheme.h>
#include <strsafe.h>
#include "minipath.h"
#include "dlapi.h"
#include "config.h"
#include "resource.h"

#ifndef TMT_MSGBOXFONT
#define TMT_MSGBOXFONT 805
#endif

// -----------------------------------------------------------------------------
#pragma warning( disable : 26451 )
// -----------------------------------------------------------------------------

extern WCHAR     g_wchIniFile[MAX_PATH];
extern WCHAR     g_UsedLngLocaleName[LOCALE_NAME_MAX_LENGTH];

//=============================================================================
//
//  BeginWaitCursor()
//
void BeginWaitCursor()
{
    DestroyCursor(SetCursor(LoadCursor(NULL,IDC_WAIT)));
}


//=============================================================================
//
//  EndWaitCursor()
//
void EndWaitCursor()
{
    DestroyCursor(SetCursor(LoadCursor(NULL,IDC_ARROW)));
}


//=============================================================================
//
//  LoadLngStringW()
//
int LoadLngStringW(UINT uID, LPWSTR lpBuffer, int nBufferMax)
{
    const int nLen = LoadStringW(g_hLngResContainer, uID, lpBuffer, nBufferMax);
    return (nLen != 0) ? nLen : LoadStringW(g_hInstance, uID, lpBuffer, nBufferMax);
}

//=============================================================================
//
//  LoadLngStringA()
//
int LoadLngStringA(UINT uID, LPSTR lpBuffer, int nBufferMax)
{
    const int nLen = LoadStringA(g_hLngResContainer, uID, lpBuffer, nBufferMax);
    return (nLen != 0) ? nLen : LoadStringA(g_hInstance, uID, lpBuffer, nBufferMax);
}


//=============================================================================
//
//  FormatLngStringW()
//
int FormatLngStringW(LPWSTR lpOutput, int nOutput, UINT uIdFormat, ...)
{
    WCHAR* pBuffer = LocalAlloc(LPTR, sizeof(WCHAR)*nOutput);
    if (pBuffer) {
        if (LoadLngStringW(uIdFormat, pBuffer, nOutput)) {
            int t = vswprintf_s(lpOutput, nOutput, pBuffer, (LPVOID)((PUINT_PTR)&uIdFormat + 1));
            lpOutput[t] = L'\0';
        }
        LocalFree(pBuffer);
        return (int)lstrlen(lpOutput);
    } else {
        return 0;
    }
}

//=============================================================================
//
//  FormatLngStringA()
//
int FormatLngStringA(LPSTR lpOutput, int nOutput, UINT uIdFormat, ...)
{
    CHAR* pBuffer = LocalAlloc(LPTR, sizeof(CHAR)*nOutput);
    if (pBuffer) {
        if (LoadLngStringA(uIdFormat, pBuffer, nOutput)) {
            int t = vsprintf_s(lpOutput, nOutput, pBuffer, (LPVOID)((PUINT_PTR)&uIdFormat + 1));
            lpOutput[t] = '\0';
        }
        LocalFree(pBuffer);
        return (int)strlen(lpOutput);
    } else {
        return 0;
    }
}


//=============================================================================
//
//  ExeNameFromWnd()
//
DWORD WINAPI GetModuleFileNameExW(HANDLE,HMODULE,LPTSTR,DWORD);
BOOL WINAPI EnumProcessModules(HANDLE,HMODULE*,DWORD,LPDWORD);

BOOL ExeNameFromWnd(HWND hwnd,LPWSTR szExeName,int cchExeName)
{
    //HMODULE hPSAPI;
    DWORD dwProcessId;
    HANDLE hProcess;
    HMODULE hModule;
    DWORD cbNeeded = 0;
    //FARPROC fpCreateToolhelp32Snapshot;
    //FARPROC fpProcess32First;
    //FARPROC fpProcess32Next;
    //HANDLE hProcessList;
    //PROCESSENTRY32 pe;
    //BOOL bMoreEntries;
    //BOOL bFoundMatching = FALSE;

    /*if (IsWindowsNT())
    {
      if (hPSAPI = LoadLibrary(L"PSAPI"))
      {*/
    GetWindowThreadProcessId(hwnd,&dwProcessId);
    hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,FALSE,dwProcessId);
    /*GetProcAddress(hPSAPI,"EnumProcessModules")*/ EnumProcessModules(hProcess,&hModule,sizeof(HMODULE),&cbNeeded);
    /*GetProcAddress(hPSAPI,"GetModuleFileNameExW")*/ GetModuleFileNameExW(hProcess,hModule,szExeName,cchExeName);
    CloseHandle(hProcess);
    //FreeLibrary(hPSAPI);
    return TRUE;
    /*}
    else
      return FALSE;
    }

    else
    {
    fpCreateToolhelp32Snapshot = GetProcAddress(GetModuleHandle(_T("kernel32.dll")),"CreateToolhelp32Snapshot");
    fpProcess32First = GetProcAddress(GetModuleHandle(_T("kernel32.dll")),"Process32First");
    fpProcess32Next = GetProcAddress(GetModuleHandle(_T("kernel32.dll")),"Process32Next");

    if (fpCreateToolhelp32Snapshot)
    {
      GetWindowThreadProcessId(hwnd,&dwProcessId);
      hProcessList = (HANDLE)fpCreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
      pe.dwSize = sizeof(PROCESSENTRY32);
      bMoreEntries = fpProcess32First(hProcessList,&pe);

      while (bMoreEntries)
      {
        if (pe.th32ProcessID == dwProcessId)
        {
          bMoreEntries = FALSE;
          bFoundMatching = TRUE;
          lstrcpyn(szExeName,pe.szExeFile,cchExeName);
        }
        else
          bMoreEntries = fpProcess32Next(hProcessList,&pe);
      }
      CloseHandle(hProcessList);
      return (bFoundMatching);
    }
    else
      return FALSE;
    }*/
}


////=============================================================================
////
////  Is32bitExe()
////
//BOOL Is32bitExe(LPCWSTR lpszExeName)
//{
//  SHFILEINFO shfi;
//  DWORD dwExeType;
//  WCHAR tch[MAX_PATH];
//
//  if (PathIsLnkFile(lpszExeName) &&
//      PathGetLnkPath(lpszExeName,tch,COUNTOF(tch)))
//    dwExeType = SHGetFileInfo(tch,0,&shfi,sizeof(SHFILEINFO),SHGFI_EXETYPE);
//  else
//    dwExeType = SHGetFileInfo(lpszExeName,0,&shfi,sizeof(SHFILEINFO),SHGFI_EXETYPE);
//
//  return (HIWORD(dwExeType) && LOWORD(dwExeType)) ? TRUE : FALSE;
//}


//=============================================================================
//
//  PrivateIsAppThemed()
//
BOOL PrivateIsAppThemed()
{
    BOOL bIsAppThemed = FALSE;
    HMODULE hDll = LoadLibrary(L"uxtheme.dll");
    if (hDll) {
        FARPROC fp = GetProcAddress(hDll,"IsAppThemed");
        if (fp) {
            bIsAppThemed = (BOOL)fp();
        }
        FreeLibrary(hDll);
    }
    return bIsAppThemed;
}


//=============================================================================
//
//  SetTheme()
//
BOOL SetTheme(HWND hwnd, LPCWSTR lpszTheme)
{
    return (S_OK == SetWindowTheme(hwnd, lpszTheme, NULL));
}


//=============================================================================
//
//  BitmapMergeAlpha()
//  Merge alpha channel into color channel
//
BOOL BitmapMergeAlpha(HBITMAP hbmp,COLORREF crDest)
{
    BITMAP bmp;
    if (GetObject(hbmp,sizeof(BITMAP),&bmp)) {

        if (bmp.bmBitsPixel == 32) {

            int x,y;
            RGBQUAD *prgba = bmp.bmBits;

            for (y = 0; y < bmp.bmHeight; y++) {
                for (x = 0; x < bmp.bmWidth; x++) {
                    BYTE alpha = prgba[x].rgbReserved;
                    prgba[x].rgbRed = ((prgba[x].rgbRed * alpha) + (GetRValue(crDest) * (255-alpha))) >> 8;
                    prgba[x].rgbGreen = ((prgba[x].rgbGreen * alpha) + (GetGValue(crDest) * (255-alpha))) >> 8;
                    prgba[x].rgbBlue = ((prgba[x].rgbBlue * alpha) + (GetBValue(crDest) * (255-alpha))) >> 8;
                    prgba[x].rgbReserved = 0xFF;
                }
                prgba = (RGBQUAD*)((LPBYTE)prgba + bmp.bmWidthBytes);
            }
            return TRUE;
        }
    }
    return FALSE;
}


//=============================================================================
//
//  BitmapAlphaBlend()
//  Perform alpha blending to color channel only
//
BOOL BitmapAlphaBlend(HBITMAP hbmp,COLORREF crDest,BYTE alpha)
{
    BITMAP bmp;
    if (GetObject(hbmp,sizeof(BITMAP),&bmp)) {

        if (bmp.bmBitsPixel == 32) {

            int x,y;
            RGBQUAD *prgba = bmp.bmBits;

            for (y = 0; y < bmp.bmHeight; y++) {
                for (x = 0; x < bmp.bmWidth; x++) {
                    prgba[x].rgbRed = ((prgba[x].rgbRed * alpha) + (GetRValue(crDest) * (255-alpha))) >> 8;
                    prgba[x].rgbGreen = ((prgba[x].rgbGreen * alpha) + (GetGValue(crDest) * (255-alpha))) >> 8;
                    prgba[x].rgbBlue = ((prgba[x].rgbBlue * alpha) + (GetBValue(crDest) * (255-alpha))) >> 8;
                }
                prgba = (RGBQUAD*)((LPBYTE)prgba + bmp.bmWidthBytes);
            }
            return TRUE;
        }
    }
    return FALSE;
}


//=============================================================================
//
//  BitmapGrayScale()
//  Gray scale color channel only
//
BOOL BitmapGrayScale(HBITMAP hbmp)
{
    BITMAP bmp;
    if (GetObject(hbmp,sizeof(BITMAP),&bmp)) {

        if (bmp.bmBitsPixel == 32) {

            int x,y;
            RGBQUAD *prgba = bmp.bmBits;

            for (y = 0; y < bmp.bmHeight; y++) {
                for (x = 0; x < bmp.bmWidth; x++) {
                    prgba[x].rgbRed = prgba[x].rgbGreen = prgba[x].rgbBlue =
                            (((BYTE)((prgba[x].rgbRed * 38 + prgba[x].rgbGreen * 75 + prgba[x].rgbBlue * 15) >> 7) * 0x80) + (0xD0 * (255-0x80))) >> 8;
                }
                prgba = (RGBQUAD*)((LPBYTE)prgba + bmp.bmWidthBytes);
            }
            return TRUE;
        }
    }
    return FALSE;
}


//=============================================================================
//
//  SetWindowPathTitle()
//
BOOL SetWindowPathTitle(HWND hwnd,LPCWSTR lpszFile)
{
    WCHAR szTitle[MAX_PATH + 120] = { L'\0' };

    if (StrIsNotEmpty(lpszFile)) {
        if (!PathIsRoot(lpszFile)) {
            SHFILEINFO shfi;
            ZeroMemory(&shfi, sizeof(SHFILEINFO));
            if (SHGetFileInfo(lpszFile, 0, &shfi, sizeof(SHFILEINFO), SHGFI_DISPLAYNAME)) {
                lstrcpy(szTitle, shfi.szDisplayName);
            } else {
                lstrcpy(szTitle, PathFindFileName(lpszFile));
            }

            WCHAR tchPath[MAX_PATH] = { L'\0' };
            lstrcpy(tchPath, lpszFile);
            PathRemoveFileSpec(tchPath);
            lstrcat(szTitle, L" - [");
            lstrcat(szTitle, tchPath);
            if (tchPath[0] && (tchPath[lstrlen(tchPath)-1] == L'\\')) {
                lstrcat(szTitle, L"]");
            } else {
                lstrcat(szTitle, L"\\]");
            }
        } else {
            lstrcpy(szTitle, lpszFile);
        }
    }

    return SetWindowText(hwnd, szTitle);
}


//=============================================================================
//
//  CenterDlgInParent()
//
void CenterDlgInParent(HWND hDlg)
{

    RECT rcDlg;
    HWND hParent;
    RECT rcParent;
    MONITORINFO mi;
    HMONITOR hMonitor;

    int xMin, yMin, xMax, yMax, x, y;

    GetWindowRect(hDlg,&rcDlg);

    hParent = GetParent(hDlg);
    GetWindowRect(hParent,&rcParent);

    hMonitor = MonitorFromRect(&rcParent,MONITOR_DEFAULTTONEAREST);
    mi.cbSize = sizeof(mi);
    GetMonitorInfo(hMonitor,&mi);

    xMin = mi.rcWork.left;
    yMin = mi.rcWork.top;

    xMax = (mi.rcWork.right) - (rcDlg.right - rcDlg.left);
    yMax = (mi.rcWork.bottom) - (rcDlg.bottom - rcDlg.top);

    if ((rcParent.right - rcParent.left) - (rcDlg.right - rcDlg.left) > 20) {
        x = rcParent.left + (((rcParent.right - rcParent.left) - (rcDlg.right - rcDlg.left)) / 2);
    } else {
        x = rcParent.left + 70;
    }

    if ((rcParent.bottom - rcParent.top) - (rcDlg.bottom - rcDlg.top) > 20) {
        y = rcParent.top  + (((rcParent.bottom - rcParent.top) - (rcDlg.bottom - rcDlg.top)) / 2);
    } else {
        y = rcParent.top + 60;
    }

    SetWindowPos(hDlg,NULL, clampi(x, xMin, xMax), clampi(y, yMin, yMax),0,0,SWP_NOZORDER|SWP_NOSIZE);

}


//=============================================================================
//
//  MakeBitmapButton()
//
void MakeBitmapButton(HWND hwnd, int nCtlId, HINSTANCE hInstance, WORD uBmpId)
{
    HWND hwndCtl = GetDlgItem(hwnd,nCtlId);
    BITMAP bmp;
    BUTTON_IMAGELIST bi;
    HBITMAP hBmp = LoadImage(hInstance,MAKEINTRESOURCE(uBmpId),IMAGE_BITMAP,0,0,LR_CREATEDIBSECTION);
    GetObject(hBmp,sizeof(BITMAP),&bmp);
    bi.himl = ImageList_Create(bmp.bmWidth,bmp.bmHeight,ILC_COLOR32|ILC_MASK,1,0);
    ImageList_AddMasked(bi.himl,hBmp,CLR_DEFAULT);
    DeleteObject(hBmp);
    SetRect(&bi.margin,0,0,0,0);
    bi.uAlign = BUTTON_IMAGELIST_ALIGN_CENTER;
    SendMessage(hwndCtl,BCM_SETIMAGELIST,0,(LPARAM)&bi);
}


//=============================================================================
//
//  DeleteBitmapButton()
//
void DeleteBitmapButton(HWND hwnd,int nCtlId)
{
    HWND hwndCtl = GetDlgItem(hwnd,nCtlId);
    BUTTON_IMAGELIST bi;
    if (SendMessage(hwndCtl,BCM_GETIMAGELIST,0,(LPARAM)&bi)) {
        ImageList_Destroy(bi.himl);
    }
}


//=============================================================================
//
//  SetWindowTransparentMode()
//
void SetWindowTransparentMode(HWND hwnd, BOOL bTransparentMode, int iOpacityLevel)
{
    if (bTransparentMode) {
        SetWindowLongPtr(hwnd, GWL_EXSTYLE, GetWindowLongPtr(hwnd, GWL_EXSTYLE) | WS_EX_LAYERED);
        BYTE const bAlpha = (BYTE)MulDiv(iOpacityLevel, 255, 100);
        SetLayeredWindowAttributes(hwnd, 0, bAlpha, LWA_ALPHA);
    } else {
        SetWindowLongPtr(hwnd, GWL_EXSTYLE, GetWindowLongPtr(hwnd, GWL_EXSTYLE) & ~WS_EX_LAYERED);
    }
}


//=============================================================================
//
//  StatusSetText()
//
BOOL StatusSetText(HWND hwnd,UINT nPart,LPCWSTR lpszText)
{
    UINT const uFlags = SBT_OWNERDRAW | nPart;
    return (BOOL)SendMessage(hwnd,SB_SETTEXT,uFlags,(LPARAM)lpszText);
}


//=============================================================================
//
//  Toolbar_Get/SetButtons()
//
int Toolbar_GetButtons(HWND hwnd,int cmdBase,LPWSTR lpszButtons,int cchButtons)
{
    WCHAR tchButtons[512];
    WCHAR tchItem[32];
    TBBUTTON tbb;

    lstrcpy(tchButtons,L"");
    int const c = min(50,(int)SendMessage(hwnd,TB_BUTTONCOUNT,0,0));

    for (int i = 0; i < c; i++) {
        SendMessage(hwnd,TB_GETBUTTON,(WPARAM)i,(LPARAM)&tbb);
        wsprintf(tchItem,L"%i ",
                 (tbb.idCommand==0)?0:tbb.idCommand-cmdBase+1);
        lstrcat(tchButtons,tchItem);
    }
    TrimStringW(tchButtons);
    (void)lstrcpyn(lpszButtons,tchButtons,cchButtons);
    return(c);
}

int Toolbar_SetButtons(HWND hwnd,int cmdBase,LPCWSTR lpszButtons,LPCTBBUTTON ptbb,int ctbb)
{
    WCHAR tchButtons[512];
    int i,c;
    int iCmd;

    ZeroMemory(tchButtons,COUNTOF(tchButtons)*sizeof(tchButtons[0]));
    (void)lstrcpyn(tchButtons,lpszButtons,COUNTOF(tchButtons)-2);
    TrimStringW(tchButtons);
    WCHAR *p = wcsstr(tchButtons, L"  ");
    while (p) {
        MoveMemory((WCHAR*)p, (WCHAR*)p + 1, (lstrlen(p) + 1) * sizeof(WCHAR));
        p = wcsstr(tchButtons, L"  ");
    }
    c = (int)SendMessage(hwnd,TB_BUTTONCOUNT,0,0);
    for (i = 0; i < c; i++) {
        SendMessage(hwnd,TB_DELETEBUTTON,0,0);
    }

    for (i = 0; i < COUNTOF(tchButtons); i++)
        if (tchButtons[i] == L' ') {
            tchButtons[i] = 0;
        }

    p = tchButtons;
    while (*p) {
        if (swscanf_s(p,L"%i",&iCmd) == 1) {
            iCmd = (iCmd==0)?0:iCmd+cmdBase-1;
            for (i = 0; i < ctbb; i++) {
                if (ptbb[i].idCommand == iCmd) {
                    SendMessage(hwnd,TB_ADDBUTTONS,(WPARAM)1,(LPARAM)&ptbb[i]);
                    break;
                }
            }
        }
        p = StrEnd(p)+1;
    }
    return((int)SendMessage(hwnd,TB_BUTTONCOUNT,0,0));
}


//=============================================================================
//
//  Toolbar_SetButtonImage()
//
void Toolbar_SetButtonImage(HWND hwnd,int idCommand,int iImage)
{
    TBBUTTONINFO tbbi;

    tbbi.cbSize = sizeof(TBBUTTONINFO);
    tbbi.dwMask = TBIF_IMAGE;
    tbbi.iImage = iImage;

    SendMessage(hwnd,TB_SETBUTTONINFO,(WPARAM)idCommand,(LPARAM)&tbbi);
}


//=============================================================================
//
//  SendWMSize()
//
LRESULT SendWMSize(HWND hwnd)
{
    RECT rc;
    GetClientRect(hwnd,&rc);
    return(SendMessage(hwnd,WM_SIZE,SIZE_RESTORED,
                       MAKELPARAM(rc.right,rc.bottom)));
}


//=============================================================================
//
//  PathRelativeToApp()
//
void PathRelativeToApp(
    LPWSTR lpszSrc,LPWSTR lpszDest,int cchDest,BOOL bSrcIsFile,
    BOOL bUnexpandEnv,BOOL bUnexpandMyDocs)
{

    WCHAR wchAppPath[MAX_PATH];
    WCHAR wchWinDir[MAX_PATH];
    WCHAR wchUserFiles[MAX_PATH];
    WCHAR wchPath[MAX_PATH];
    WCHAR wchResult[MAX_PATH];
    DWORD dwAttrTo = (bSrcIsFile) ? 0 : FILE_ATTRIBUTE_DIRECTORY;

    GetModuleFileName(NULL,wchAppPath,COUNTOF(wchAppPath));
    PathRemoveFileSpec(wchAppPath);
    (void)GetWindowsDirectory(wchWinDir,COUNTOF(wchWinDir));
    SHGetFolderPath(NULL,CSIDL_PERSONAL,NULL,SHGFP_TYPE_CURRENT,wchUserFiles);

    if (bUnexpandMyDocs &&
            !PathIsRelative(lpszSrc) &&
            !PathIsPrefix(wchUserFiles,wchAppPath) &&
            PathIsPrefix(wchUserFiles,lpszSrc) &&
            PathRelativePathTo(wchPath,wchUserFiles,FILE_ATTRIBUTE_DIRECTORY,lpszSrc,dwAttrTo)) {
        lstrcpy(wchUserFiles,L"%CSIDL:MYDOCUMENTS%");
        PathAppend(wchUserFiles,wchPath);
        lstrcpy(wchPath,wchUserFiles);
    } else if (PathIsRelative(lpszSrc) || PathCommonPrefix(wchAppPath,wchWinDir,NULL)) {
        (void)lstrcpyn(wchPath,lpszSrc,COUNTOF(wchPath));
    } else {
        if (!PathRelativePathTo(wchPath,wchAppPath,FILE_ATTRIBUTE_DIRECTORY,lpszSrc,dwAttrTo)) {
            (void)lstrcpyn(wchPath,lpszSrc,COUNTOF(wchPath));
        }
    }

    if (bUnexpandEnv) {
        if (!PathUnExpandEnvStrings(wchPath,wchResult,COUNTOF(wchResult))) {
            (void)lstrcpyn(wchResult,wchPath,COUNTOF(wchResult));
        }
    } else {
        (void)lstrcpyn(wchResult,wchPath,COUNTOF(wchResult));
    }

    if (lpszDest == NULL || lpszSrc == lpszDest) {
        (void)lstrcpyn(lpszSrc,wchResult,(cchDest == 0) ? MAX_PATH : cchDest);
    } else {
        (void)lstrcpyn(lpszDest,wchResult,(cchDest == 0) ? MAX_PATH : cchDest);
    }
}


//=============================================================================
//
//  PathAbsoluteFromApp()
//
void PathAbsoluteFromApp(LPWSTR lpszSrc,LPWSTR lpszDest,int cchDest,BOOL bExpandEnv)
{

    WCHAR wchPath[MAX_PATH];
    WCHAR wchResult[MAX_PATH];

    if (StrCmpNI(lpszSrc,L"%CSIDL:MYDOCUMENTS%",CSTRLEN("%CSIDL:MYDOCUMENTS%")) == 0) {
        SHGetFolderPath(NULL,CSIDL_PERSONAL,NULL,SHGFP_TYPE_CURRENT,wchPath);
        PathAppend(wchPath,lpszSrc+ CSTRLEN("%CSIDL:MYDOCUMENTS%"));
    } else {
        (void)lstrcpyn(wchPath,lpszSrc,COUNTOF(wchPath));
    }

    if (bExpandEnv) {
        ExpandEnvironmentStringsEx(wchPath,COUNTOF(wchPath));
    }

    if (PathIsRelative(wchPath)) {
        GetModuleFileName(NULL,wchResult,COUNTOF(wchResult));
        PathRemoveFileSpec(wchResult);
        PathAppend(wchResult,wchPath);
    } else {
        (void)lstrcpyn(wchResult,wchPath,COUNTOF(wchResult));
    }

    PathCanonicalizeEx(wchResult);
    if (PathGetDriveNumber(wchResult) != -1) {
        CharUpperBuff(wchResult,1);
    }

    if (lpszDest == NULL || lpszSrc == lpszDest) {
        (void)lstrcpyn(lpszSrc,wchResult,(cchDest == 0) ? MAX_PATH : cchDest);
    } else {
        (void)lstrcpyn(lpszDest,wchResult,(cchDest == 0) ? MAX_PATH : cchDest);
    }
}


///////////////////////////////////////////////////////////////////////////////
//
//
//  Name: PathIsLnkFile()
//
//  Purpose: Determine wheter pszPath is a Windows Shell Link File by
//           comparing the filename extension with L".lnk"
//
//  Manipulates:
//
BOOL PathIsLnkFile(LPCWSTR pszPath)
{

    //WCHAR *pszExt;

    WCHAR tchResPath[256];

    if (!pszPath || !*pszPath) {
        return FALSE;
    }

    /*pszExt = strrchr(pszPath,L'.');

      if (!pszExt)
        return FALSE;

      if (!lstrcmpi(pszExt,L".lnk"))
        return TRUE;

      else
        return FALSE;*/

    //if (!lstrcmpi(PathFindExtension(pszPath),L".lnk"))
    //  return TRUE;

    //else
    //  return FALSE;

    if (lstrcmpi(PathFindExtension(pszPath),L".lnk")) {
        return FALSE;
    }

    else {
        return PathGetLnkPath(pszPath,tchResPath,COUNTOF(tchResPath));
    }

}


///////////////////////////////////////////////////////////////////////////////
//
//
//  Name: PathGetLnkPath()
//
//  Purpose: Try to get the path to which a lnk-file is linked
//
//
//  Manipulates: pszResPath
//
BOOL PathGetLnkPath(LPCWSTR pszLnkFile,LPWSTR pszResPath,int cchResPath)
{

    IShellLink       *psl;
    WIN32_FIND_DATA  fd;
    BOOL             bSucceeded = FALSE;

    if (SUCCEEDED(CoCreateInstance(&CLSID_ShellLink,NULL,
                                   CLSCTX_INPROC_SERVER,
                                   &IID_IShellLink,&psl))) {
        IPersistFile *ppf;

        if (SUCCEEDED(psl->lpVtbl->QueryInterface(psl,&IID_IPersistFile,&ppf))) {
            WORD wsz[MAX_PATH];

            /*MultiByteToWideChar(CP_ACP,MB_PRECOMPOSED,pszLnkFile,-1,wsz,MAX_PATH);*/
            lstrcpy(wsz,pszLnkFile);

            if (SUCCEEDED(ppf->lpVtbl->Load(ppf,wsz,STGM_READ))) {
                if (NOERROR == psl->lpVtbl->GetPath(psl,pszResPath,cchResPath,&fd,0)) {
                    bSucceeded = TRUE;
                }
            }
            ppf->lpVtbl->Release(ppf);
        }
        psl->lpVtbl->Release(psl);
    }

    // This additional check seems reasonable
    if (StrIsEmpty(pszResPath)) {
        bSucceeded = FALSE;
    }

    if (bSucceeded) {
        ExpandEnvironmentStringsEx(pszResPath,cchResPath);
    }

    return(bSucceeded);

}


///////////////////////////////////////////////////////////////////////////////
//
//
//  Name: PathIsLnkToDirectory()
//
//  Purpose: Determine wheter pszPath is a Windows Shell Link File which
//           refers to a directory
//
//  Manipulates: pszResPath
//
BOOL PathIsLnkToDirectory(LPCWSTR pszPath,LPWSTR pszResPath,int cchResPath)
{

    WCHAR tchResPath[MAX_PATH];

    if (PathIsLnkFile(pszPath)) {

        if (PathGetLnkPath(pszPath,tchResPath,sizeof(WCHAR)*COUNTOF(tchResPath))) {

            if (PathIsDirectory(tchResPath)) {

                (void)lstrcpyn(pszResPath,tchResPath,cchResPath);
                return (TRUE);
            }

            else {
                return FALSE;
            }
        }

        else {
            return FALSE;
        }
    }

    else {
        return FALSE;
    }

}


///////////////////////////////////////////////////////////////////////////////
//
//
//  Name: PathCreateLnk()
//
//  Purpose: Try to create a lnk-file in the specified directory referring
//           to the specified file or directory
//
//  Manipulates:
//
BOOL PathCreateLnk(LPCWSTR pszLnkDir,LPCWSTR pszPath)
{

    WCHAR tchLnkFileName[MAX_PATH];

    IShellLink *psl;
    BOOL bSucceeded = FALSE;
    BOOL fMustCopy;

    // Try to construct a valid filename...
    if (!SHGetNewLinkInfo(pszPath,pszLnkDir,tchLnkFileName,&fMustCopy,SHGNLI_PREFIXNAME)) {
        return(FALSE);
    }

    if (SUCCEEDED(CoCreateInstance(&CLSID_ShellLink,NULL,
                                   CLSCTX_INPROC_SERVER,
                                   &IID_IShellLink,&psl))) {
        IPersistFile *ppf;

        if (SUCCEEDED(psl->lpVtbl->QueryInterface(psl,&IID_IPersistFile,&ppf))) {
            WORD wsz[MAX_PATH];

            /*MultiByteToWideChar(CP_ACP,MB_PRECOMPOSED,tchLnkFileName,-1,wsz,MAX_PATH);*/
            lstrcpy(wsz,tchLnkFileName);

            if (NOERROR == psl->lpVtbl->SetPath(psl,pszPath) &&
                    SUCCEEDED(ppf->lpVtbl->Save(ppf,wsz,TRUE))) {
                bSucceeded = TRUE;
            }

            ppf->lpVtbl->Release(ppf);
        }
        psl->lpVtbl->Release(psl);
    }

    return(bSucceeded);

}


//=============================================================================
//
//  TrimStringW()
//
BOOL TrimStringW(LPWSTR lpString)
{

    LPWSTR psz;

    if (!lpString || !*lpString) {
        return FALSE;
    }

    // Trim left
    psz = lpString;

    while (*psz == L' ') {
        psz = CharNext(psz);
    }

    MoveMemory(lpString,psz,sizeof(WCHAR)*(lstrlen(psz) + 1));

    // Trim right
    psz = StrEnd(lpString);

    while (*(psz = CharPrev(lpString,psz)) == L' ') {
        *psz = L'\0';
    }

    return TRUE;

}


//=============================================================================
//
//  ExtractFirstArgument()
//
BOOL ExtractFirstArgument(LPCWSTR lpArgs,LPWSTR lpArg1,LPWSTR lpArg2)
{

    LPWSTR psz;
    BOOL bQuoted = FALSE;

    lstrcpy(lpArg1,lpArgs);
    if (lpArg2) {
        *lpArg2 = L'\0';
    }

    if (!TrimStringW(lpArg1)) {
        return FALSE;
    }

    if (*lpArg1 == L'\"') {
        *lpArg1 = L' ';
        TrimStringW(lpArg1);
        bQuoted = TRUE;
    }

    if (bQuoted) {
        psz = StrChr(lpArg1,L'\"');
    } else {
        psz = StrChr(lpArg1,L' ');
    };

    if (psz) {
        *psz = L'\0';
        if (lpArg2) {
            lstrcpy(lpArg2,psz + 1);
        }
    }

    TrimStringW(lpArg1);

    if (lpArg2) {
        TrimStringW(lpArg2);
    }

    return TRUE;

}


//=============================================================================
//
//  QuotateFilenameStr()
//
LPWSTR QuotateFilenameStr(LPWSTR lpFile)
{
    if (StrChr(lpFile,L' ')) {
        MoveMemory(lpFile + 1,lpFile,sizeof(WCHAR)*(lstrlen(lpFile) + 1));
        *lpFile = '\"';
        lstrcat(lpFile,L"\"");
    }
    return lpFile;
}


//=============================================================================
//
//  GetFilenameStr()
//
LPWSTR GetFilenameStr(LPWSTR lpFile)
{
    LPWSTR psz = StrEnd(lpFile);

    while (psz != lpFile && *CharPrev(lpFile,psz) != L'\\') {
        psz = CharPrev(lpFile,psz);
    }

    return psz;
}


//=============================================================================
//
//  PrepareFilterStr()
//
void PrepareFilterStr(LPWSTR lpFilter)
{
    LPWSTR psz = StrEnd(lpFilter);
    while (psz != lpFilter) {
        if (*(psz = CharPrev(lpFilter,psz)) == L'\n') {
            *psz = L'\0';
        }
    }
}


//=============================================================================
//
//  StrTab2Space() - in place conversion
//
void StrTab2Space(LPWSTR lpsz)
{
    WCHAR *c = StrChr(lpsz, L'\t');
    while (c) {
        *c = L' ';
        c = StrChr(lpsz, L'\t');
    }
}


//=============================================================================
//
//  ExpandEnvironmentStringsEx()
//
//  Adjusted for Windows 95
//
void ExpandEnvironmentStringsEx(LPWSTR lpSrc,DWORD dwSrc)
{
    WCHAR szBuf[312];

    if (ExpandEnvironmentStrings(lpSrc,szBuf,COUNTOF(szBuf))) {
        (void)lstrcpyn(lpSrc,szBuf,dwSrc);
    }
}


//=============================================================================
//
//  PathCanonicalizeEx()
//
//
void PathCanonicalizeEx(LPWSTR lpSrc)
{
    WCHAR szDst[MAX_PATH];

    if (PathCanonicalize(szDst,lpSrc)) {
        lstrcpy(lpSrc,szDst);
    }
}


//=============================================================================
//
//  SearchPathEx()
//
//  This Expansion also searches the L"Favorites" folder
//

BOOL SearchPathEx(LPCWSTR lpFileName, DWORD nBufferLength, LPWSTR lpBuffer)
{
    DWORD dwRetVal = 0;

    if (StrEqual(lpFileName, L"..") || StrEqual(lpFileName, L".")) {
        if (StrEqual(lpFileName, L"..") && PathIsRoot(Settings.szCurDir)) {
            (void)lstrcpyn(lpBuffer, L"*.*", nBufferLength);
            dwRetVal = 1;
        }
    }

    if (!dwRetVal) {
        dwRetVal = SearchPath(Settings.szCurDir, lpFileName, NULL, nBufferLength, lpBuffer, NULL);
    }

    // Search Favorites if no result
    if (!dwRetVal) {
        dwRetVal = SearchPath(Settings.tchFavoritesDir, lpFileName, NULL, nBufferLength, lpBuffer, NULL);
    }

    return dwRetVal != 0;
}


//=============================================================================
//
//  FormatNumberStr()
//
int FormatNumberStr(LPWSTR lpNumberStr)
{
    WCHAR *c;
    WCHAR szSep[8];
    int  i = 0;

    if (StrIsEmpty(lpNumberStr)) {
        return(0);
    }

    if (!GetLocaleInfoEx(LOCALE_NAME_USER_DEFAULT, LOCALE_STHOUSAND, szSep, COUNTOF(szSep))) {
        szSep[0] = L'\'';
    }
    c = StrEnd(lpNumberStr);

    while ((c = CharPrev(lpNumberStr,c)) != lpNumberStr) {
        if (++i == 3) {
            i = 0;
            MoveMemory(c+1,c,sizeof(WCHAR)*(lstrlen(c)+1));
            *c = szSep[0];
        }
    }

    return(lstrlen(lpNumberStr));
}


//=============================================================================
//
//  GetDefaultFavoritesDir()
//
void GetDefaultFavoritesDir(LPWSTR lpFavDir,int cchFavDir)
{
    LPITEMIDLIST pidl;
    if (NOERROR == SHGetSpecialFolderLocation(
                NULL,CSIDL_PERSONAL,&pidl)) {
        SHGetPathFromIDList(pidl,lpFavDir);
        CoTaskMemFree(pidl);
    } else {
        (void)GetWindowsDirectory(lpFavDir,cchFavDir);
    }
}


//=============================================================================
//
//  GetDefaultOpenWithDir()
//
void GetDefaultOpenWithDir(LPWSTR lpOpenWithDir,int cchOpenWithDir)
{

    LPITEMIDLIST pidl;

    if (NOERROR == SHGetSpecialFolderLocation(
                NULL,CSIDL_DESKTOPDIRECTORY,&pidl)) {
        SHGetPathFromIDList(pidl,lpOpenWithDir);
        CoTaskMemFree(pidl);
    }

    else {
        (void)GetWindowsDirectory(lpOpenWithDir,cchOpenWithDir);
    }

}


//=============================================================================
//
//  CreateDropHandle()
//
//  Creates a HDROP to generate a WM_DROPFILES message
//
//
HDROP CreateDropHandle(LPCWSTR lpFileName)
{
    HGLOBAL hDrop = GlobalAlloc(GMEM_ZEROINIT | GMEM_MOVEABLE | GMEM_DDESHARE,
                                sizeof(DROPFILES) + sizeof(WCHAR) * (lstrlen(lpFileName) + 2));

    if (hDrop) {
        LPDROPFILES lpdf = GlobalLock(hDrop);
        if (lpdf) {
            lpdf->pFiles = sizeof(DROPFILES);
            lpdf->pt.x = 0;
            lpdf->pt.y = 0;
            lpdf->fNC = TRUE;
            lpdf->fWide = TRUE;
            lstrcpy((WCHAR*)&lpdf[1], lpFileName);
        }
        GlobalUnlock(hDrop);
    }
    return(hDrop);
}


//=============================================================================
//
//  DirList_IsFileSelected()
//
//  Check if a file is selected in the DirList
//
//
BOOL DirList_IsFileSelected(HWND hwnd)
{
    int i;
    DLITEM dli;

    i = (ListView_GetSelectedCount(hwnd));
    dli.mask = DLI_TYPE;
    dli.ntype = DLE_NONE;
    DirList_GetItem(hwnd,-1,&dli);

    return (i && dli.ntype == DLE_FILE);
}


//=============================================================================
//
//  ExecDDECommand()
//
//  Execute a DDE command (Msg,App,Topic)
//
//

HDDEDATA CALLBACK DdeCallback(UINT uType, UINT uFmt, HCONV hconv, HSZ hsz1, HSZ hsz2,
                              HDDEDATA hdata, ULONG_PTR  dwData1, ULONG_PTR  dwData2)
{
    switch (uType) {
    case XTYP_ADVDATA:
        return (HDDEDATA)DDE_FACK;

    default:
        return (HDDEDATA)NULL;
    }
    UNUSED(uFmt);
    UNUSED(hconv);
    UNUSED(hsz1);
    UNUSED(hsz2);
    UNUSED(hdata);
    UNUSED(dwData1);
    UNUSED(dwData2);
}

BOOL ExecDDECommand(LPCWSTR lpszCmdLine,
                    LPCWSTR lpszDDEMsg,LPCWSTR lpszDDEApp,LPCWSTR lpszDDETopic)
{

    WCHAR  lpszURLExec[512];
    WCHAR  lpszDDEMsgBuf[256];
    WCHAR  *pSubst;
    DWORD idInst=0;
    HSZ hszService, hszTopic;
    HCONV hConv;
    BOOL bSuccess = TRUE;

    if (StrIsEmpty(lpszCmdLine) || StrIsEmpty(lpszDDEMsg) ||
            StrIsEmpty(lpszDDEApp) || StrIsEmpty(lpszDDETopic)) {
        return FALSE;
    }

    (void)lstrcpyn(lpszDDEMsgBuf,lpszDDEMsg,COUNTOF(lpszDDEMsgBuf));
    pSubst = wcsstr(lpszDDEMsgBuf, L"%1");
    if (pSubst) {
        *(pSubst+1) = L's';
    }

    wsprintf(lpszURLExec,lpszDDEMsgBuf,lpszCmdLine);

    if (DdeInitialize(&idInst, DdeCallback,
                      APPCLASS_STANDARD | APPCMD_CLIENTONLY, 0L) == DMLERR_NO_ERROR) {
        hszService = DdeCreateStringHandle(idInst, lpszDDEApp, CP_WINUNICODE);
        hszTopic = DdeCreateStringHandle(idInst, lpszDDETopic, CP_WINUNICODE);
        if (hszService && hszTopic) {
            hConv = DdeConnect(idInst, hszService, hszTopic, NULL);
            if (hConv) {
                DdeClientTransaction((LPBYTE)lpszURLExec,sizeof(WCHAR)*(lstrlen(lpszURLExec) + 1),
                                     hConv, 0, 0, XTYP_EXECUTE, TIMEOUT_ASYNC, NULL);
                DdeDisconnect(hConv);
            } else {
                bSuccess = FALSE;
            }
        }

        if (hszTopic) {
            DdeFreeStringHandle(idInst, hszTopic);
        }
        if (hszService) {
            DdeFreeStringHandle(idInst, hszService);
        }
        DdeUninitialize(idInst);
    }

    return bSuccess;
}


//=============================================================================
//
//  History Functions
//
//
BOOL History_Init(PHISTORY ph)
{
    if (!ph) {
        return FALSE;
    }

    ZeroMemory(ph,sizeof(HISTORY));
    ph->iCurItem = (-1);

    return TRUE;
}

BOOL History_Uninit(PHISTORY ph)
{
    int i;

    if (!ph) {
        return FALSE;
    }

    for (i = 0; i < HISTORY_ITEMS; i++) {
        if (ph->psz[i]) {
            LocalFree(ph->psz[i]);
        }
        ph->psz[i] = NULL;
    }

    return TRUE;
}

BOOL History_Add(PHISTORY ph,LPCWSTR pszNew)
{
    int i;

    if (!ph) {
        return FALSE;
    }

    // Item to be added is equal to current item
    if ((ph->iCurItem >= 0) && (ph->iCurItem < HISTORY_ITEMS))
        if (!lstrcmpi(pszNew,ph->psz[ph->iCurItem])) {
            return FALSE;
        }

    if (ph->iCurItem == (HISTORY_ITEMS-1)) {
        // Shift
        if (ph->psz[0]) {
            LocalFree(ph->psz[0]);
        }

        MoveMemory(ph->psz,ph->psz+1,(HISTORY_ITEMS-1)*sizeof(WCHAR*));
    }

    else {
        ph->iCurItem++;
        for (i = ph->iCurItem; i < HISTORY_ITEMS; i++) {
            if (ph->psz[i]) {
                LocalFree(ph->psz[i]);
            }
            ph->psz[i] = NULL;
        }
    }

    if ((ph->iCurItem >= 0) && (ph->iCurItem < HISTORY_ITEMS)) {
        ph->psz[ph->iCurItem] = StrDup(pszNew);
    }

    return TRUE;
}

BOOL History_Forward(PHISTORY ph,LPWSTR pszItem,int cItem)
{
    if (!ph) {
        return FALSE;
    }

    if (ph->iCurItem < (HISTORY_ITEMS-1))
        if (ph->psz[ph->iCurItem+1]) {
            ph->iCurItem++;
            (void)lstrcpyn(pszItem,ph->psz[ph->iCurItem],cItem);
            return TRUE;
        }

    return FALSE;
}

BOOL History_Back(PHISTORY ph,LPWSTR pszItem,int cItem)
{
    if (!ph) {
        return FALSE;
    }

    if (ph->iCurItem > 0)
        if (ph->psz[ph->iCurItem-1]) {
            ph->iCurItem--;
            (void)lstrcpyn(pszItem,ph->psz[ph->iCurItem],cItem);
            return TRUE;
        }

    return FALSE;
}

BOOL History_CanForward(PHISTORY ph)
{
    if (!ph) {
        return FALSE;
    }

    if (ph->iCurItem < (HISTORY_ITEMS-1))
        if (ph->psz[ph->iCurItem+1]) {
            return TRUE;
        }

    return FALSE;
}

BOOL History_CanBack(PHISTORY ph)
{
    if (!ph) {
        return FALSE;
    }

    if (ph->iCurItem > 0)
        if (ph->psz[ph->iCurItem-1]) {
            return TRUE;
        }

    return FALSE;
}

void History_UpdateToolbar(PHISTORY ph,HWND hwnd,int cmdBack,int cmdForward)
{
    if (History_CanBack(ph)) {
        SendMessage(hwnd,TB_ENABLEBUTTON,cmdBack,MAKELONG(1,0));
    } else {
        SendMessage(hwnd,TB_ENABLEBUTTON,cmdBack,MAKELONG(0,0));
    }

    if (History_CanForward(ph)) {
        SendMessage(hwnd,TB_ENABLEBUTTON,cmdForward,MAKELONG(1,0));
    } else {
        SendMessage(hwnd,TB_ENABLEBUTTON,cmdForward,MAKELONG(0,0));
    }
}


//=============================================================================
//
//  MRU functions
//
LPMRULIST MRU_Create(LPCWSTR pszRegKey,int iFlags,int iSize)
{

    LPMRULIST pmru = LocalAlloc(LPTR,sizeof(MRULIST));
    if (pmru) {
        ZeroMemory(pmru, sizeof(MRULIST));
        (void)lstrcpyn(pmru->szRegKey, pszRegKey, COUNTOF(pmru->szRegKey));
        pmru->iFlags = iFlags;
        pmru->iSize = min(iSize, MRU_MAXITEMS);
    }
    return(pmru);
}

BOOL MRU_Destroy(LPMRULIST pmru)
{

    int i;
    for (i = 0; i < pmru->iSize; i++) {
        if (pmru->pszItems[i]) {
            LocalFree((LPWSTR)pmru->pszItems[i]);
        }
    }
    ZeroMemory(pmru,sizeof(MRULIST));
    LocalFree(pmru);
    return(1);
}

int MRU_Compare(LPMRULIST pmru,LPCWSTR psz1,LPCWSTR psz2)
{

    if (pmru->iFlags & MRU_NOCASE) {
        return(lstrcmpi(psz1,psz2));
    } else {
        return(lstrcmp(psz1,psz2));
    }
}

BOOL MRU_Add(LPMRULIST pmru,LPCWSTR pszNew)
{

    int i;
    for (i = 0; i < pmru->iSize; i++) {
        if (MRU_Compare(pmru,pmru->pszItems[i],pszNew) == 0) {
            LocalFree((LPWSTR)pmru->pszItems[i]);
            break;
        }
    }
    i = min(i,pmru->iSize-1);
    for (; i > 0; i--) {
        pmru->pszItems[i] = pmru->pszItems[i-1];
    }
    pmru->pszItems[0] = StrDup(pszNew);
    return(1);
}

BOOL MRU_Delete(LPMRULIST pmru,int iIndex)
{

    int i;
    if (iIndex < 0 || iIndex > pmru->iSize-1) {
        return(0);
    }
    if (pmru->pszItems[iIndex]) {
        LocalFree((LPWSTR)pmru->pszItems[iIndex]);
    }
    for (i = iIndex; i < pmru->iSize-1; i++) {
        pmru->pszItems[i] = pmru->pszItems[i+1];
        pmru->pszItems[i+1] = NULL;
    }
    return(1);
}

BOOL MRU_Empty(LPMRULIST pmru)
{

    int i;
    for (i = 0; i < pmru->iSize; i++) {
        if (pmru->pszItems[i]) {
            LocalFree((LPWSTR)pmru->pszItems[i]);
            pmru->pszItems[i] = NULL;
        }
    }
    return(1);
}

int MRU_Enum(LPMRULIST pmru,int iIndex,LPWSTR pszItem,int cchItem)
{

    if (pszItem == NULL || cchItem == 0) {
        int i = 0;
        while ((i < pmru->iSize) && (pmru->pszItems[i])) {
            ++i;
        }
        return(i);
    } else {
        if (iIndex < 0 || iIndex > pmru->iSize-1 || !pmru->pszItems[iIndex]) {
            return(-1);
        } else {
            (void)lstrcpyn(pszItem,pmru->pszItems[iIndex],cchItem);
            return(lstrlen(pszItem));
        }
    }
}

BOOL MRU_Load(LPMRULIST pmru)
{

    int i, n = 0;
    WCHAR tchName[32];
    WCHAR tchItem[1024];

    MRU_Empty(pmru);
    __try {
        LoadIniFileCache(g_wchIniFile);

        const WCHAR* const RegKey_Section = pmru->szRegKey;

        for (i = 0; i < pmru->iSize; i++) {
            StringCchPrintf(tchName, COUNTOF(tchName), L"%.2i", i + 1);
            if (IniSectionGetString(RegKey_Section, tchName, L"", tchItem, COUNTOF(tchItem))) {
                size_t const len = (size_t)lstrlen(tchItem);
                if ((len > 0) && (tchItem[0] == L'"') && (tchItem[len - 1] == L'"')) {
                    MoveMemory(tchItem, (tchItem + 1), len * sizeof(WCHAR));
                    tchItem[len - 2] = L'\0'; // clear dangling '"'
                }
                pmru->pszItems[n++] = StrDup(tchItem);
            }
        }
    } __finally {
        ResetIniFileCache();
    }
    return(1);
}

BOOL MRU_Save(LPMRULIST pmru)
{

    __try {
        LoadIniFileCache(g_wchIniFile);

        WCHAR tchName[32];
        WCHAR tchItem[1024] = { L'\0' };

        const WCHAR* const RegKey_Section = pmru->szRegKey;
        IniSectionClear(pmru->szRegKey, FALSE);

        for (int i = 0; i < pmru->iSize; i++) {
            if (pmru->pszItems[i]) {
                StringCchPrintf(tchName, COUNTOF(tchName), L"%.2i", i + 1);
                StringCchPrintf(tchItem, COUNTOF(tchItem), L"\"%s\"", pmru->pszItems[i]);
                IniSectionSetString(RegKey_Section, tchName, tchItem);
            }
        }
    } __finally {
        SaveIniFileCache(g_wchIniFile);
    }
    return TRUE;
}

void MRU_LoadToCombobox(HWND hwnd,LPCWSTR pszKey)
{
    int i;
    WCHAR tch[MAX_PATH];
    LPMRULIST pmru = MRU_Create(pszKey,MRU_NOCASE,8);
    MRU_Load(pmru);
    for (i = 0; i < MRU_Count(pmru); i++) {
        MRU_Enum(pmru,i,tch,COUNTOF(tch));
        SendMessage(hwnd,CB_ADDSTRING,0,(LPARAM)tch);
    }
    MRU_Destroy(pmru);
}


void MRU_AddOneItem(LPCWSTR pszKey,LPCWSTR pszNewItem)
{
    if (StrIsNotEmpty(pszNewItem)) {
        LPMRULIST pmru = MRU_Create(pszKey,MRU_NOCASE,8);
        MRU_Load(pmru);
        MRU_Add(pmru,pszNewItem);
        MRU_Save(pmru);
        MRU_Destroy(pmru);
    }
}


//=============================================================================
//
//  IsFontAvailable()
//  Test if a certain font is installed on the system
//
static int CALLBACK EnumFontsProc(CONST LOGFONT* plf, CONST TEXTMETRIC* ptm, DWORD FontType, LPARAM lParam)
{
    UNUSED(plf);
    UNUSED(ptm);
    UNUSED(FontType);
    *((PBOOL)lParam) = TRUE;
    return 0;
}

BOOL IsFontAvailable(LPCWSTR lpszFontName)
{
    BOOL fFound = FALSE;
    HDC const hDC = GetDC(NULL);
    EnumFonts(hDC, lpszFontName, EnumFontsProc, (LPARAM)&fFound);
    ReleaseDC(NULL, hDC);
    return fFound;
}


/*

  Themed Dialogs
  Modify dialog templates to use current theme font
  Based on code of MFC helper class CDialogTemplate

*/

static inline BOOL IsChineseTraditionalSubLang(LANGID subLang)
{
    return subLang == SUBLANG_CHINESE_TRADITIONAL || subLang == SUBLANG_CHINESE_HONGKONG || subLang == SUBLANG_CHINESE_MACAU;
}

BOOL GetLocaleDefaultUIFont(LANGID lang, LPWSTR lpFaceName, WORD* wSize)
{
    LPCWSTR font;
    LANGID const subLang = SUBLANGID(lang);
    switch (PRIMARYLANGID(lang)) {
    default:
    case LANG_ENGLISH:
        font = L"Segoe UI";
        *wSize = 9;
        break;
    case LANG_CHINESE:
        font = IsChineseTraditionalSubLang(subLang) ? L"Microsoft JhengHei UI" : L"Microsoft YaHei UI";
        *wSize = 9;
        break;
    case LANG_JAPANESE:
        font = L"Yu Gothic UI";
        *wSize = 9;
        break;
    case LANG_KOREAN:
        font = L"Malgun Gothic";
        *wSize = 9;
        break;
    }
    BOOL const isAvail = IsFontAvailable(font);
    if (isAvail) {
        StringCchCopy(lpFaceName, LF_FACESIZE, font);
    }
    return isAvail;
}


BOOL GetThemedDialogFont(LPWSTR lpFaceName, WORD* wSize)
{
    // deprecated:
    LANGID const langID = GetLangIdByLocaleName(g_UsedLngLocaleName);

    BOOL bSucceed = GetLocaleDefaultUIFont(langID, lpFaceName, wSize);

    HDC hDC = GetDC(NULL);
    int const iLogPixelsY = GetDeviceCaps(hDC, LOGPIXELSY);
    ReleaseDC(NULL, hDC);

    if (!bSucceed) {
        HTHEME hTheme = OpenThemeData(NULL, L"WINDOWSTYLE;WINDOW");
        if (hTheme) {
            LOGFONT lf;
            if (S_OK == GetThemeSysFont(hTheme, TMT_MSGBOXFONT, &lf)) {
                if (lf.lfHeight < 0) {
                    lf.lfHeight = -lf.lfHeight;
                }
                *wSize = (WORD)MulDiv(lf.lfHeight, 72, iLogPixelsY);
                if (*wSize < 9) {
                    *wSize = 9;
                }
                StringCchCopy(lpFaceName, LF_FACESIZE, lf.lfFaceName);
                bSucceed = TRUE;
            }
            CloseThemeData(hTheme);
        }
    }

    if (!bSucceed) {
        NONCLIENTMETRICS ncm = {0};
        ncm.cbSize = sizeof(NONCLIENTMETRICS) - sizeof(ncm.iPaddedBorderWidth);
        SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &ncm, 0);
        if (ncm.lfMessageFont.lfHeight < 0) {
            ncm.lfMessageFont.lfHeight = -ncm.lfMessageFont.lfHeight;
        }
        *wSize = (WORD)MulDiv(ncm.lfMessageFont.lfHeight, 72, iLogPixelsY);
        if (*wSize < 9) {
            *wSize = 9;
        }
        StringCchCopy(lpFaceName, LF_FACESIZE, ncm.lfMessageFont.lfFaceName);
        bSucceed = TRUE;
    }

    return bSucceed;
}


static inline BOOL DialogTemplate_IsDialogEx(const DLGTEMPLATE* pTemplate)
{

    return ((DLGTEMPLATEEX*)pTemplate)->signature == 0xFFFF;
}

static inline BOOL DialogTemplate_HasFont(const DLGTEMPLATE* pTemplate)
{

    return (DS_SETFONT &
            (DialogTemplate_IsDialogEx(pTemplate) ? ((DLGTEMPLATEEX*)pTemplate)->style : pTemplate->style));
}

static inline int DialogTemplate_FontAttrSize(BOOL bDialogEx)
{

    return (int)sizeof(WORD) * (bDialogEx ? 3 : 1);
}

static inline BYTE* DialogTemplate_GetFontSizeField(const DLGTEMPLATE* pTemplate)
{

    BOOL bDialogEx = DialogTemplate_IsDialogEx(pTemplate);
    WORD* pw;

    if (bDialogEx) {
        pw = (WORD*)((DLGTEMPLATEEX*)pTemplate + 1);
    } else {
        pw = (WORD*)(pTemplate + 1);
    }

    if (*pw == (WORD)-1) {
        pw += 2;
    } else
        while(*pw++);

    if (*pw == (WORD)-1) {
        pw += 2;
    } else
        while(*pw++);

    while (*pw++);

    return (BYTE*)pw;
}


DLGTEMPLATE* LoadThemedDialogTemplate(LPCTSTR lpDialogTemplateID, HINSTANCE hInstance)
{
    HRSRC const hRsrc = FindResource(hInstance, lpDialogTemplateID, RT_DIALOG);
    if (!hRsrc) {
        return(NULL);
    }

    HGLOBAL const hRsrcMem = LoadResource(hInstance,hRsrc);
    DLGTEMPLATE* const pRsrcMem = hRsrcMem ? (DLGTEMPLATE*)LockResource(hRsrcMem) : NULL;
    if (!pRsrcMem) {
        return NULL;
    }

    size_t const dwTemplateSize = (size_t)SizeofResource(hInstance,hRsrc);
    DLGTEMPLATE* const pTemplate = dwTemplateSize ? (DLGTEMPLATE*)LocalAlloc(LPTR, dwTemplateSize + LF_FACESIZE * 2) : NULL;

    if (!pTemplate) {
        UnlockResource(hRsrcMem);
        FreeResource(hRsrcMem);
        return NULL;
    }

    CopyMemory((BYTE*)pTemplate,pRsrcMem,dwTemplateSize);
    UnlockResource(hRsrcMem);
    FreeResource(hRsrcMem);

    WCHAR wchFaceName[LF_FACESIZE] = { L'\0' };
    WORD  wFontSize = 0;
    if (!GetThemedDialogFont(wchFaceName, &wFontSize)) {
        return (pTemplate);
    }

    BOOL const bDialogEx = DialogTemplate_IsDialogEx(pTemplate);
    BOOL const bHasFont = DialogTemplate_HasFont(pTemplate);
    size_t const cbFontAttr = DialogTemplate_FontAttrSize(bDialogEx);

    if (bDialogEx) {
        ((DLGTEMPLATEEX*)pTemplate)->style |= DS_SHELLFONT;
    } else {
        pTemplate->style |= DS_SHELLFONT;
    }

    size_t const cbNew = cbFontAttr + ((lstrlen(wchFaceName) + 1) * sizeof(WCHAR));
    BYTE* const pbNew = (BYTE*)wchFaceName;

    BYTE* pb = DialogTemplate_GetFontSizeField(pTemplate);
    size_t const cbOld = (bHasFont ? cbFontAttr + 2 * (lstrlen((WCHAR*)(pb + cbFontAttr)) + 1) : 0);

    BYTE* const pOldControls = (BYTE*)(((DWORD_PTR)pb + cbOld + 3) & ~(DWORD_PTR)3);
    BYTE* const pNewControls = (BYTE*)(((DWORD_PTR)pb + cbNew + 3) & ~(DWORD_PTR)3);

    WORD const nCtrl = (bDialogEx ? ((DLGTEMPLATEEX*)pTemplate)->cDlgItems : pTemplate->cdit);

    if (cbNew != cbOld && nCtrl > 0) {
        MoveMemory(pNewControls, pOldControls, (dwTemplateSize - (pOldControls - (BYTE*)pTemplate)));
    }

    *(WORD*)pb = wFontSize;
    MoveMemory(pb + cbFontAttr, pbNew, (size_t)(cbNew - cbFontAttr));

    return pTemplate;
}


INT_PTR ThemedDialogBoxParam(HINSTANCE hInstance, LPCTSTR lpTemplate, HWND hWndParent,
                             DLGPROC lpDialogFunc, LPARAM dwInitParam)
{

    DLGTEMPLATE* const pDlgTemplate = LoadThemedDialogTemplate(lpTemplate,hInstance);
    INT_PTR const ret = DialogBoxIndirectParam(hInstance, pDlgTemplate, hWndParent, lpDialogFunc, dwInitParam);
    if (pDlgTemplate) {
        LocalFree(pDlgTemplate);
    }
    return ret;
}

/*

  MinimizeToTray - Copyright 2000 Matthew Ellis <m.t.ellis@bigfoot.com>

  Changes made by flo:
   - Commented out: #include "stdafx.h"
   - Moved variable declaration: APPBARDATA appBarData;
   - Declared GetDoAnimateMinimize() as non-static

*/

// MinimizeToTray
//
// A couple of routines to show how to make it produce a custom caption
// animation to make it look like we are minimizing to and maximizing
// from the system tray
//
// These routines are public domain, but it would be nice if you dropped
// me a line if you use them!
//
// 1.0 29.06.2000 Initial version
// 1.1 01.07.2000 The window retains it's place in the Z-order of windows
//     when minimized/hidden. This means that when restored/shown, it doen't
//     always appear as the foreground window unless we call SetForegroundWindow
//
// Copyright 2000 Matthew Ellis <m.t.ellis@bigfoot.com>
/*#include "stdafx.h"*/

// Odd. VC++6 winuser.h has IDANI_CAPTION defined (as well as IDANI_OPEN and
// IDANI_CLOSE), but the Platform SDK only has IDANI_OPEN...

// I don't know what IDANI_OPEN or IDANI_CLOSE do. Trying them in this code
// produces nothing. Perhaps they were intended for window opening and closing
// like the MAC provides...
#ifndef IDANI_OPEN
#define IDANI_OPEN 1
#endif
#ifndef IDANI_CLOSE
#define IDANI_CLOSE 2
#endif
#ifndef IDANI_CAPTION
#define IDANI_CAPTION 3
#endif

#define DEFAULT_RECT_WIDTH 150
#define DEFAULT_RECT_HEIGHT 30

// Returns the rect of where we think the system tray is. This will work for
// all current versions of the shell. If explorer isn't running, we try our
// best to work with a 3rd party shell. If we still can't find anything, we
// return a rect in the lower right hand corner of the screen
static VOID GetTrayWndRect(LPRECT lpTrayRect)
{
    APPBARDATA appBarData;
    // First, we'll use a quick hack method. We know that the taskbar is a window
    // of class Shell_TrayWnd, and the status tray is a child of this of class
    // TrayNotifyWnd. This provides us a window rect to minimize to. Note, however,
    // that this is not guaranteed to work on future versions of the shell. If we
    // use this method, make sure we have a backup!
    HWND hShellTrayWnd=FindWindowEx(NULL,NULL,TEXT("Shell_TrayWnd"),NULL);
    if(hShellTrayWnd) {
        HWND hTrayNotifyWnd=FindWindowEx(hShellTrayWnd,NULL,TEXT("TrayNotifyWnd"),NULL);
        if(hTrayNotifyWnd) {
            GetWindowRect(hTrayNotifyWnd,lpTrayRect);
            return;
        }
    }

    // OK, we failed to get the rect from the quick hack. Either explorer isn't
    // running or it's a new version of the shell with the window class names
    // changed (how dare Microsoft change these undocumented class names!) So, we
    // try to find out what side of the screen the taskbar is connected to. We
    // know that the system tray is either on the right or the bottom of the
    // taskbar, so we can make a good guess at where to minimize to
    /*APPBARDATA appBarData;*/
    appBarData.cbSize=sizeof(appBarData);
    if(SHAppBarMessage(ABM_GETTASKBARPOS,&appBarData)) {
        // We know the edge the taskbar is connected to, so guess the rect of the
        // system tray. Use various fudge factor to make it look good
        switch(appBarData.uEdge) {
        case ABE_LEFT:
        case ABE_RIGHT:
            // We want to minimize to the bottom of the taskbar
            lpTrayRect->top=appBarData.rc.bottom-100;
            lpTrayRect->bottom=appBarData.rc.bottom-16;
            lpTrayRect->left=appBarData.rc.left;
            lpTrayRect->right=appBarData.rc.right;
            break;

        case ABE_TOP:
        case ABE_BOTTOM:
            // We want to minimize to the right of the taskbar
            lpTrayRect->top=appBarData.rc.top;
            lpTrayRect->bottom=appBarData.rc.bottom;
            lpTrayRect->left=appBarData.rc.right-100;
            lpTrayRect->right=appBarData.rc.right-16;
            break;
        }

        return;
    }

    // Blimey, we really aren't in luck. It's possible that a third party shell
    // is running instead of explorer. This shell might provide support for the
    // system tray, by providing a Shell_TrayWnd window (which receives the
    // messages for the icons) So, look for a Shell_TrayWnd window and work out
    // the rect from that. Remember that explorer's taskbar is the Shell_TrayWnd,
    // and stretches either the width or the height of the screen. We can't rely
    // on the 3rd party shell's Shell_TrayWnd doing the same, in fact, we can't
    // rely on it being any size. The best we can do is just blindly use the
    // window rect, perhaps limiting the width and height to, say 150 square.
    // Note that if the 3rd party shell supports the same configuraion as
    // explorer (the icons hosted in NotifyTrayWnd, which is a child window of
    // Shell_TrayWnd), we would already have caught it above
    hShellTrayWnd=FindWindowEx(NULL,NULL,TEXT("Shell_TrayWnd"),NULL);
    if(hShellTrayWnd) {
        GetWindowRect(hShellTrayWnd,lpTrayRect);
        if(lpTrayRect->right-lpTrayRect->left>DEFAULT_RECT_WIDTH) {
            lpTrayRect->left=lpTrayRect->right-DEFAULT_RECT_WIDTH;
        }
        if(lpTrayRect->bottom-lpTrayRect->top>DEFAULT_RECT_HEIGHT) {
            lpTrayRect->top=lpTrayRect->bottom-DEFAULT_RECT_HEIGHT;
        }

        return;
    }

    // OK. Haven't found a thing. Provide a default rect based on the current work
    // area
    SystemParametersInfo(SPI_GETWORKAREA,0,lpTrayRect,0);
    lpTrayRect->left=lpTrayRect->right-DEFAULT_RECT_WIDTH;
    lpTrayRect->top=lpTrayRect->bottom-DEFAULT_RECT_HEIGHT;
}

// Check to see if the animation has been disabled
/*static */BOOL GetDoAnimateMinimize(VOID)
{
    ANIMATIONINFO ai;

    ai.cbSize=sizeof(ai);
    SystemParametersInfo(SPI_GETANIMATION,sizeof(ai),&ai,0);

    return ai.iMinAnimate?TRUE:FALSE;
}

VOID MinimizeWndToTray(HWND hWnd)
{
    if(GetDoAnimateMinimize()) {
        RECT rcFrom,rcTo;

        // Get the rect of the window. It is safe to use the rect of the whole
        // window - DrawAnimatedRects will only draw the caption
        GetWindowRect(hWnd,&rcFrom);
        GetTrayWndRect(&rcTo);

        // Get the system to draw our animation for us
        DrawAnimatedRects(hWnd,IDANI_CAPTION,&rcFrom,&rcTo);
    }

    // Add the tray icon. If we add it before the call to DrawAnimatedRects,
    // the taskbar gets erased, but doesn't get redrawn until DAR finishes.
    // This looks untidy, so call the functions in this order

    // Hide the window
    ShowWindow(hWnd,SW_HIDE);
}

VOID RestoreWndFromTray(HWND hWnd)
{
    if(GetDoAnimateMinimize()) {
        // Get the rect of the tray and the window. Note that the window rect
        // is still valid even though the window is hidden
        RECT rcFrom,rcTo;
        GetTrayWndRect(&rcFrom);
        GetWindowRect(hWnd,&rcTo);

        // Get the system to draw our animation for us
        DrawAnimatedRects(hWnd,IDANI_CAPTION,&rcFrom,&rcTo);
    }

    // Show the window, and make sure we're the foreground window
    ShowWindow(hWnd,SW_SHOW);
    SetActiveWindow(hWnd);
    SetForegroundWindow(hWnd);

    // Remove the tray icon. As described above, remove the icon after the
    // call to DrawAnimatedRects, or the taskbar will not refresh itself
    // properly until DAR finished
}

//=============================================================================
//
//  Find next token in string
//

CHAR* StrNextTokA(CHAR* strg, const CHAR* tokens)
{
    CHAR* n = NULL;
    const CHAR* t = tokens;
    while (t && *t) {
        CHAR* const f = StrChrA(strg, *t);
        if (!n || (f && (f < n))) {
            n = f;
        }
        ++t;
    }
    return n;
}

WCHAR* StrNextTokW(WCHAR* strg, const WCHAR* tokens)
{
    WCHAR* n = NULL;
    const WCHAR* t = tokens;
    while (t && *t) {
        WCHAR* const f = StrChrW(strg, *t);
        if (!n || (f && (f < n))) {
            n = f;
        }
        ++t;
    }
    return n;
}


///   End of Helpers.c   \\\
